package com.googlecode.jsonld4j.context;

import static com.googlecode.jsonld4j.Keywords.CONTAINER;
import static com.googlecode.jsonld4j.Keywords.ID;
import static com.googlecode.jsonld4j.Keywords.LANGUAGE;
import static com.googlecode.jsonld4j.Keywords.TYPE;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import com.googlecode.jsonld4j.ContainerType;
import com.googlecode.jsonld4j.Keywords;

/**
 * A Context is used to map terms,i.e. properties with associated values
 * in a JSON document, to IRIs. 
 * 
 * @author Stephane Fellah
 *
 */
public class Context {

	public static String NULL = "null";
	
	private Map<String,Object> ctxMap = new HashMap<String,Object>();	
	private String source;
	private String defaultLanguage = null;
	private Context parentCtx;
	private String baseIRI;
	
	public Context() {
		
	}
	
	public Context(Context parentCtx) {
		this.parentCtx = parentCtx;
	}
	
	public Context getParentContext() {
		return parentCtx;
	}

	
	public String getSourceURL() {
		return source;
	}
	

	public void setSourceURL(String url) {
		this.source = url;
	}
	
	public void setBaseURI(String baseIRI) {
		this.baseIRI = baseIRI;
	}
	
	public String getBaseIRI() {
		if (baseIRI==null && parentCtx!=null) {
			return parentCtx.getBaseIRI();
		}
		if (baseIRI.equals(NULL))
			return null;
		return baseIRI;
	}
	
	
	public Set<String> getTerms() {
		Set<String> terms = new HashSet<String>();
		for(String term : ctxMap.keySet()) {
			terms.add(term);
		}
		if (parentCtx!=null) {
			terms.addAll(parentCtx.getTerms());
		}
		return terms;
	}
	
	public String getDefaultLanguage() {
		if (defaultLanguage!=null)
			return defaultLanguage;
		if (parentCtx!=null)
			return parentCtx.getDefaultLanguage();
		return null;
	}

	public void setDefaultLanguage(String lang) {
		defaultLanguage = lang;		
	}
	
	
	public void set(String term,String uri) {
		Object o = ctxMap.get(term);
		if (o==null || o  instanceof String) {
			ctxMap.put(term,uri);
		}
		setId(term,uri);
	}
	
	
	public void setId(String term,String id) {
		Map<String,String> map = getTermDefinition(term);
		map.put(ID,id);
		ctxMap.put(term,map);
	}
	
	
	public String getId(String term) {
		String id = getTermDefinition(term).get(ID);
		if (id!=null) {
			return expandIRI(id);
		}
		if (parentCtx!=null)
			return parentCtx.getId(term);
		return null;
	}
	
	
	public void setType(String term,String type) {
		Map<String,String> map = getTermDefinition(term);
		map.put(TYPE,type);
		ctxMap.put(term,map);
	}
	
	public String getType(String term) {
		String type = getTermDefinition(term).get(TYPE);
		if ( type!=null)
			return  type;
		if (parentCtx!=null)
			return parentCtx.getType(term);
		return null;
	}
	
	
	@SuppressWarnings("unchecked")
	public void setLanguage(String term,String lang) {
		Map<String,String> map = getTermDefinition(term);
		map.put(LANGUAGE,lang);	
		ctxMap.put(term,map);
	}

	
	public String getLanguage(String term) {
		String lang =  getTermDefinition(term).get(LANGUAGE);
		if ( lang!=null) {
			// language is not inherited if NULL value is set.
			if (lang.equals(NULL))
				return null;
			return  lang;
		}
		if (parentCtx!=null)
			return parentCtx.getLanguage(lang);
		return defaultLanguage;
	}
	
	
	public void setContainerType(String term,ContainerType containerType ) {
		Map<String,String> map = getTermDefinition(term);
		map.put(Keywords.CONTAINER,containerType.toString());
		ctxMap.put(term,map);
	}
	
	
	public boolean isContainer(String term) {
		return getTermDefinition(term).get(CONTAINER)!=null;
		
	}
	
	
	public ContainerType getContainerType(String term) {
		String containerType = getTermDefinition(term).get(CONTAINER);
		if (containerType!=null) 
			return Enum.valueOf(ContainerType.class, containerType);
		return null;
	}
	
	@SuppressWarnings("unchecked")
	public Map<String,String> getTermDefinition(String term) {
		Object o = ctxMap.get(term);
		Map<String,String> map = null;
		if (o==null) {
			if (parentCtx!=null) {
				return parentCtx.getTermDefinition(term);
			}
			return new HashMap<String,String>();
		}	
		if (o instanceof Map) {
			return (Map)o;
		}
		if (o instanceof String) {
			map = new HashMap<String,String>();
			map.put(Keywords.ID, (String)o);
			return map;
		}	
		return map;
	}

	public Context merge(Context context) {
		Context ctx = new Context(this);
		ctx.baseIRI = context.baseIRI ;
		ctx.ctxMap = context.ctxMap ;
		ctx.defaultLanguage = context.defaultLanguage ;
		ctx.source = source ;
		return ctx;
	}
	
	
	public String expandIRI(String term) {
		String uri = getId(term);
		if (uri!=null)
			return uri;
		int index = term.indexOf(":");
		if (index!=-1) {
			String prefix = term.substring(0,index);
			String suffix = term.substring(index+1);
			if (suffix.startsWith("//"))
				return term;
			String namespace = getId(prefix);
			if (namespace!=null)
				return namespace +suffix;
		}	
		if (baseIRI!=null)
			return baseIRI+term;
		return term;
	}
		
	
	public String toString() {
		StringBuffer b = new StringBuffer("{\n");
		for(String key : getTerms()) {
			b.append(key).append(":").append(getTermDefinition(key)).append("\n");
		}
		b.append("}\n");
		if (parentCtx!=null) 
			b.append("parentCtx:"+parentCtx.toString());
		return b.toString();
	}

	
	
}
